package com.griddroid.serverdaemons;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.net.ServerSocket;
import java.net.Socket;
import com.griddroid.configuration.GridConfig;
import com.griddroid.messages.GenericMessage;
import com.griddroid.messages.StartSessionRequestMessage;
import com.griddroid.nodes.GridNode;
import com.griddroid.nodes.GridServer;
import com.griddroid.serverdaemons.threadhandlers.ClientHandler;
import com.griddroid.serverdaemons.threadhandlers.WorkerHandler;

/**
 * This class represents a NodeManagerDaemon of a {@link GridServer GridServer}. It extends the abstract class
 * {@link GridNode GridNode} to gain the main functionality of. Its work is to accept connection for new nodes 
 * wanting to connect to the Grid system.
 * 
 * @author Charalampos Mavroforakis
 * @author Thomas Ailianos
 * @author Michail Michailidis
 */
public class NodeManagerDaemon extends GenericDaemon
{
	private ServerSocket mainSocket;
	private ObjectInputStream instream;
	private Socket nodeSocket;

	/**
	 * Constructs a NodeManagerDaemon and binds to a given port from the {@link GridConfig GridConfig}
	 * @param server
	 * 		server to be installed to.
	 * @throws IOException
	 * 		when it cannot start a {@link ServerSocket ServerSocket} to accept new connections.
	 */
	public NodeManagerDaemon(GridServer server) throws IOException
	{
		server.setNodeManager(this);
		mainSocket = new ServerSocket(GridConfig.SERVERPORT);
		this.server = server;
	}

	/**
	 * Starts the NodeManagerDaemon accepting new connections for new nodes 
	 * wanting to join the grid and starts a Handler ({@link ClientHandler ClientHanlder} or a {@link WorkerHandler WorkerHandler})
	 */
	@Override
	public void run()
	{
		try
		{
			while (true)
			{
				nodeSocket = null;
				try
				{
					nodeSocket = this.mainSocket.accept();
					this.server.log("Info", "Manager:New request accepted");
				}
				catch (IOException e)
				{
					this.server.log("Error", "Could not accept connections. Error:" + e.getMessage());
				}

				try
				{
					instream = new ObjectInputStream(nodeSocket.getInputStream());
				}
				catch (IOException e)
				{
					this.server.log("Error", "Could not open Object input Stream. Error:" + e.getMessage());
				}

				GenericMessage mess = null;
				try
				{
					mess = this.receive();
					if (!(mess instanceof StartSessionRequestMessage))
						this.server.log("Error", "Could not retrieve StartSessionMessage.");

				}
				catch (IOException e1)
				{
					this.server.log("Error", "Message isn't StartSessionRequestMessage . Error:" + e1.getMessage());
				}
				catch (ClassNotFoundException e1)
				{
					this.server.log("Error", "Could not retrieve StartSessionMessage. Error:" + e1.getMessage());
				}
				switch (((StartSessionRequestMessage) mess).getType())
				{
				case CLIENT:
					ClientHandler node = new ClientHandler(nodeSocket, this.server);
					Thread clientThread = new Thread(node);
					clientThread.start();
					this.server.getClientDaemon().AddClient(node);
					break;
				case WORKER:
					try
					{
						WorkerHandler wnode = new WorkerHandler(nodeSocket, this.server);
						Thread workerThread = new Thread(wnode);
						workerThread.start();
						this.server.getWorkerDaemon().AddWorker(wnode);
					}
					catch (IOException e)
					{
						this.server.log("Error", "Could not add worker. Error:" + e.getMessage());
					}
					break;
				default:
					this.server.log("Error", "Could not define type of connecting node.");
				}
			}
		}
		catch (NullPointerException ex)
		{
		}
	}

	/**
	 * Receives a message for the server socket probably containing 
	 * a {@link StartSessionRequestMessage StartSessionRequestMessage}
	 * @return
	 * 		{@link GenericMessage GenericMessage} just received
	 * @throws IOException
	 * 		when it cannot receive a message 
	 * @throws ClassNotFoundException
	 * 		when it cannot receive a message 
	 */
	public GenericMessage receive() throws IOException, ClassNotFoundException
	{
		Object a = instream.readObject();
		if (a == null || !(a instanceof GenericMessage))
		{
			this.server.log("Error", "Stream has not generic message");
			return null;
		}
		return (GenericMessage) a;
	}
	
	/**
	 * Closes the daemon after closing all stream and sockets tha are open.
	 */
	@Override
	public void close()
	{
		try
		{
			if (this.instream != null)
			{
				this.instream.close();

			}

			if (this.mainSocket != null)
			{
				this.mainSocket.close();
			}

			if (this.nodeSocket != null)
			{
				this.nodeSocket.close();
			}
		}
		catch (IOException e)
		{

		}

	}
}
